package com.xt.base.reflection;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import com.xt.base.reflection.UV.Tuple;
import com.xt.base.util.DateUtils;

public class BaseReflector {
	
	protected BaseReflector(){};
	
	private final static ClassLoader 
		loader = Thread.currentThread().getContextClassLoader();
	
	static public boolean is(Class<?> clazz1,Class<?> clazz2){
		if(clazz1 == null || clazz2 == null)return false;
		boolean a = clazz1.isInterface(),b = clazz2.isInterface();
		if(a == b)return classEquals(clazz1,clazz2);
		Class clazz = a?clazz2:clazz1,inter = a?clazz1:clazz2;
		return isImplement(clazz,inter);
	}
	
	static public boolean isCollection(Class<?> clazz){
		return isImplement(clazz,Collection.class);
	}
	
	static public boolean classEquals(Class<?> a,Class<?> b){
		if(a == null || b == null)return false;
		if(a.equals(b))return true;
		return a.isAssignableFrom(b) || b.isAssignableFrom(a);
	}
	
	static public boolean isImplement(Class<?> clazz,Class<?> interfice){
		if(clazz == null || interfice == null)return false;
		return interfice.isAssignableFrom(clazz);
	}
	
	@SuppressWarnings("unchecked")
	static public<T> Class<T> getModel(String name)throws ClassNotFoundException{
		return (Class<T>)Class.forName(name);
	}
	
	@SuppressWarnings("unchecked")
	static public<T> T getInstance(String name)throws ClassNotFoundException,
		IllegalAccessException,InstantiationException,InvocationTargetException,NoSuchMethodException{
		return (T)getInstance(loader.loadClass(name));
	}
	
	@SuppressWarnings("unchecked")
	static public<T> T getInstance(String name,Object[] args)throws ClassNotFoundException,
		IllegalAccessException,InstantiationException,InvocationTargetException,NoSuchMethodException{
		return (T)getInstance(loader.loadClass(name),args);
	}
	
	@SuppressWarnings("unchecked")
	static public<T> T  getInstance(Class<T> clazz)throws ClassNotFoundException,
		IllegalAccessException,InstantiationException,InvocationTargetException,NoSuchMethodException{
		try{
			Method instanceMethod = clazz.getMethod("getInstance", (Class[])null);
			if(instanceMethod != null){
				T objec = (T)instanceMethod.invoke(null, (Object[])null);
				return objec;
			}else throw new RuntimeException();
		}catch(Exception e){
			return checkModel(clazz).newInstance();
		}
	}

	static public<T> T getInstance(Class<T> clazz,Object[] args)throws ClassNotFoundException,
		IllegalAccessException,InstantiationException,InvocationTargetException,NoSuchMethodException{
		Class[] clas = args != null && args.length > 0?Apply.apply(args, 
			Object.class.getMethod("getClass", (Class[])null), new Class[args.length]):null;
		return getInstance(clazz,clas,args);
	}
	
	static public<T> T  getInstance(Class<T> clazz,Class[] clas,Object[] args)throws ClassNotFoundException,
		IllegalAccessException,InstantiationException,InvocationTargetException,NoSuchMethodException{
		return checkModel(clazz).getConstructor(checkTypes(clas, args)).newInstance(args);
	}
	
	static public Field getField(Class<?> clazz,String name){
		try{
			return clazz.getDeclaredField(name);
		}catch(NoSuchFieldException e){
			return null;
		}
	}

	static public Field getFieldFromAssign(Class<?> clazz,String name){
		Field field = getField(clazz,name);
		if(field == null){
			Class sClass = clazz.getSuperclass();
			if(sClass != null)return getFieldFromAssign(sClass,name);
		}
		return field;
	}

	static public Field[] getAllField(Class<?> clazz){
		return getAllField(clazz,null);
	}
	
	static public Field[] getAllField(Class<?> clazz,String[] ignoreFields){
		if(clazz.isInterface())
			throw new IllegalArgumentException("[" + clazz.getName() + 
					"]is an interface,cannot getFiled.");
		List<Field> res = new ArrayList<Field>();
		while(clazz != null){
			Field[] fields = clazz.getDeclaredFields();
			for(Field field:fields)
				if(ignoreFields == null || !Apply.any(ignoreFields, field.getName()))res.add(field);
			clazz = clazz.getSuperclass();
		}
		return res.toArray(new Field[res.size()]);
	}
	
	@SuppressWarnings("unchecked")
	static public<T> T getValue(Object obj,Field field)
		throws NoSuchFieldException,IllegalAccessException{
		Tuple<Boolean,T> o = invokeValue(obj, field);
		if(o.a)return o.b;
		if(!Modifier.isPublic(field.getModifiers()))
			throw new IllegalArgumentException("[" + obj + "]'s field [" + 
					field.getName() + "] is not public,so can not get the value.");
		return (T)field.get(obj);
	}

	@SuppressWarnings("unchecked")
	static public<T> T getValueIgnoreAccessable(Object obj,Field field)
		throws NoSuchFieldException,IllegalAccessException{
		Tuple<Boolean,T> o = invokeValue(obj, field);
		if(o.a)return o.b;
		if(!Modifier.isPublic(field.getModifiers()))
			field.setAccessible(true);
		return (T)field.get(obj);
	}

	static public void setValue(Object obj,String fName,Object value)
		throws NoSuchFieldException,IllegalAccessException{
		Field field = getFieldFromAssign(obj.getClass(), fName);
		setValue(obj,field,value);		
	}
	
	static public void setValueIgnoreAccessable(Object obj,String fName,Object value)
		throws NoSuchFieldException,IllegalAccessException{
		Field field = getFieldFromAssign(obj.getClass(), fName);
		setValueIgnoreAccessable(obj,field,value);		
	}
	
	static public void setValue(Object obj,Field field,Object value)
		throws NoSuchFieldException,IllegalAccessException{
		if(!invokeSetValue(obj,field,value)){
			if(!Modifier.isPublic(field.getModifiers()))
				throw new IllegalArgumentException("Field [" + 
						field.getName() + "] is not public,so can not set the value.");
			field.set(obj, handleValue(field,value));
		}
	}

	static public void setValueIgnoreAccessable(Object obj,Field field,Object value)
		throws NoSuchFieldException,IllegalAccessException{
		if(!invokeSetValue(obj,field,value)){
			if(!getFieldFromAssign(obj.getClass(),field.getName()).equals(field))
				throw new IllegalArgumentException("[" + field.getName() + 
						"] is not the field of [" + obj.getClass().getName() + "]");
			if(!Modifier.isPublic(field.getModifiers()))
				field.setAccessible(true);
			field.set(obj, handleValue(field,value));
		}
	}
	
	static private Method getMethod(Class clazz,String mName,Class[] argClass){
		try{
			return clazz.getDeclaredMethod(mName,(Class[])null);			
		}catch(Exception e){
			return null;
		}
	}
	
	static public boolean hasGetMethod(Class clazz,String fieldName){
		try{
			String pName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
			Method method = getMethod(clazz,pName,(Class[])null);
			if(method != null)return true;
			Class sClazz = clazz.getSuperclass();
			if(sClazz == null && clazz.isInterface()){
				Class[] is = clazz.getInterfaces();
				if(is.length > 0){
					for(Class c:is)
						if(getMethod(c, pName, (Class[])null) != null)
								return true;
				}
				return false;
			}
			if(sClazz != null)return hasGetMethod(sClazz,fieldName);
			return false;
		}catch(Exception e){
			return false;
		}
	}
	
	static public Class[] classes(String[] packageNames, String realPath){
		Set<Class> resSet = null;
		for(String packageName:packageNames){
			Class[] classes = classes(packageName,realPath);
			if(classes != null && classes.length > 0){
				if(resSet == null)
					resSet = new HashSet<Class>();
				for(Class clazz:classes)
					resSet.add(clazz);
			}
		}
		return resSet == null?new Class[0]:resSet.toArray(new Class[resSet.size()]);
	}
	
	static public Class[] classes(String packageName, String realPath){
		realPath += StringUtils.replace(packageName, ".", "/");
		File path = new File(realPath);
		Class[] classes = null;
		if (path.isDirectory()) {
			String[] files = path.list();
			classes = new Class[files.length];
			for (int i = 0; i < files.length; i++) {
				try{
					classes[i] = getModel(packageName + "." + files[i].split("[.]")[0]);
				}catch(Exception e){
					classes[i] = null;
				}
			}
		}
		return classes;
	}

	@SuppressWarnings("unchecked")
	static private Object handleValue(Field field,Object value){
		Class clazz = field.getType();
		if(value == null)return null;
		if(clazz.isInstance(value))return value;
		try{
			String res = String.valueOf(value);
			if(clazz == String.class)return res;
			if(clazz == Date.class)return DateUtils.parse(res,"yyyy-MM-dd HH:mm:ss");
			return Simple.valueOf(clazz, value);
		}catch(Exception e){
			throw new IllegalArgumentException("IllegalArgument:Field[" + 
					field.getName() + "],type[" + field.getType() + "],value[" + value + "]");
		}
	}
	
	static public boolean isSimpleType(Class clazz){
		return Simple.is(clazz) || clazz == String.class;
	}
	
	static private<T> Class<T> checkModel(Class<T> clazz){
		if(Modifier.isAbstract(clazz.getModifiers()))
			throw new IllegalArgumentException("[" + clazz.getName() + 
					"] is an abstract class,can not instance it.");
		else if (Modifier.isStatic(clazz.getModifiers()))
			throw new IllegalArgumentException("[" + clazz.getName() + 
					"] is a static class,can not instance it.");
		return clazz;
	}
	
	static private Class[] checkTypes(Class[] clas,Object[] args){
		if(clas != null){
			if(args == null || clas.length != args.length)
				throw new IllegalArgumentException("length of types is diffrent to args.");
			for(int i=0;i<clas.length;i++)
				if(!clas[i].isInstance(args[i]))
					throw new IllegalArgumentException("type is diffrent to args.");
		}
		return clas;
	}
	
	static private boolean invokeSetValue(Object object,Field field,Object value){
		try{
			String name = field.getName(),pName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
			Method method = object.getClass().getDeclaredMethod(pName,new Class[]{field.getType()});
			if(!Modifier.isPublic(method.getModifiers()))method.setAccessible(true);
			method.invoke(object,new Object[]{value});
			return true;
		}catch(NoSuchMethodException e){
			return false;
		}catch(IllegalAccessException e){
			return false;
		}catch(InvocationTargetException e){
			return false;
		}catch(IllegalArgumentException e){
			return false;
		}
	}
	
	static public<T> T getValueIgnoreAccessable(Object object,String propertyName){
		try{
			T res = null;
			if(propertyName.indexOf('.') != -1){
				String[] names = propertyName.split("\\.");
				for(int i=0;i<names.length;i++){
					res = BaseReflector._getValueIgnoreAccessable(object, names[i]);
					if(res == null)break;
					else object = res;
				}
			}else res = BaseReflector._getValueIgnoreAccessable(object, propertyName);
			return res;
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	static public<T> T getValue(Object object,String propertyName){
		try{
			T res = null;
			if(propertyName.indexOf('.') != -1){
				String[] names = propertyName.split("\\.");
				for(int i=0;i<names.length;i++){
					res = BaseReflector._getValue(object, names[i]);
					if(res == null)break;
					else object = res;
				}
			}else res = BaseReflector._getValue(object, propertyName);
			return res;
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	public static String[] getClassNames(String[] packageNames, String realPath) {
		Set<String> classNames = null;
		for(String packageName:packageNames){
			String[] names = getClassNames(packageName,realPath);
			if(names != null && names.length > 0){
				if(classNames == null)
					classNames = new HashSet<String>();
				for(String name:names)
					classNames.add(name);
			}
		}
		return classNames.toArray(new String[0]);
	}

	public static String[] getClassNames(String packageName, String realPath) {
		realPath += StringUtils.replace(packageName, "\\.", "/");
		File path1 = new File(realPath);
		String[] classNames = null;
		if (path1.isDirectory()) {
			String[] files = path1.list();
			classNames = new String[files.length];
			for (int i = 0; i < files.length; i++) {
				classNames[i] = files[i].split("[\\.]")[0];
			}
		}
		return classNames;
	}

	/**
	 * 这里，注意packageName需要全路径才行 取得当前包下的所有类名数组
	 * 
	 * @param packageName
	 * @return
	 */
	public static String[] getClassNames(String packageName) {
		ClassLoader loader = BaseReflector.class.getClassLoader();
		String path = loader.getResource("\\.").getPath().substring(1);
		path += StringUtils.replace(packageName, "\\.", "/");
		File path1 = new File(path);
		String[] classNames = null;
		if (path1.isDirectory()) {
			String[] files = path1.list();
			classNames = new String[files.length];
			for (int i = 0; i < files.length; i++) {
				classNames[i] = files[i].split("[\\.]")[0];
			}
		}
		return classNames;
	}

	/**
	 * @param className
	 *            with full package path
	 * @return
	 */
	public static boolean classExist(String className) {
		try {
			Class clazz = Class.forName(className);
			return clazz != null;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}
	
	public static String[] getFieldNames(String className) throws ClassNotFoundException {
		if (classExist(className)) {
			Class clazz = Class.forName(className);
			Field[] flds = clazz.getDeclaredFields();
			String[] result = new String[flds.length];
			for (int i = 0; i < flds.length; i++) {
				result[i] = flds[i].getName();
			}
			return result;
		}
		return null;
	}
	
	static private<T> T _getValue(Object obj,String name)
		throws NoSuchFieldException,IllegalAccessException{
		return getValue(obj,getFieldFromAssign(obj.getClass(),name));
	}

	static private<T> T _getValueIgnoreAccessable(Object obj,String name)
		throws NoSuchFieldException,IllegalAccessException{
		return getValueIgnoreAccessable(obj,getFieldFromAssign(obj.getClass(),name));
	}
	
	@SuppressWarnings("unchecked")
	static private<T> Tuple<Boolean,T> invokeValue(Object object,Field field){
		try{
			String name = field.getName(),
			pName = (field.getType() == boolean.class?"is":"get") + 
				name.substring(0, 1).toUpperCase() + name.substring(1);
			Method method = object.getClass().getDeclaredMethod(pName, (Class[])null);
			if(!Modifier.isPublic(method.getModifiers()))method.setAccessible(true);
			return new Tuple<Boolean,T>(true,(T)method.invoke(object, (Object[])null));
		}catch(NoSuchMethodException e){
			return new Tuple<Boolean,T>(false,null);
		}catch(IllegalAccessException e){
			return new Tuple<Boolean,T>(false,null);
		}catch(InvocationTargetException e){
			return new Tuple<Boolean,T>(false,null);
		}
	}
	
	/**
	 * 通过对象（未知类型）、方法名、参数、参数类型来执行方法。
	 * @param o 对象（未知类型）
	 * @param methodName 方法名
	 * @param param 参数
	 * @param clazzs 参数类型
	 */
	public static Object invoke(Object o,String methodName,Object param,Class<?> clazzs){
		try{
			Method method = o.getClass().getDeclaredMethod(methodName, clazzs);
			return method.invoke(o, param);
		}catch(NoSuchMethodException e){
			e.printStackTrace();
		}catch(IllegalAccessException e){
			e.printStackTrace();
		}catch(InvocationTargetException e){
			e.printStackTrace();
		}
		return null;
	}
	
}
